#include <dspSimulator/instruction/mpyus.h>

bool MPYUS::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    return checkStandardCondition(dspMachine);
}

void MPYUS::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                              std::shared_ptr<llvm::IRBuilder<>> irBuilder) {
    llvm::FunctionType *funcType = llvm::FunctionType::get(irBuilder->getVoidTy(), false);
    // ---------------------------------MPYUS (.unit) ulsb16,xslsb16, sint ---------------------------------------------
    {
        llvm::Function *mpyusM = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "MPYUSKind1",
                                                        module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", mpyusM);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto src1RegPtr = getSrc1LoRegPtr(module, irBuilder);
        auto src1Reg = irBuilder->CreateLoad(src1RegPtr);

        auto src1LSB = irBuilder->CreateTrunc(src1Reg, irBuilder->getIntNTy(16));
        auto mul1 = irBuilder->CreateZExt(src1LSB, irBuilder->getInt32Ty());
        auto src2LSB = irBuilder->CreateTrunc(src2Reg, irBuilder->getIntNTy(16));
        auto mul2 = irBuilder->CreateSExt(src2LSB, irBuilder->getInt32Ty());
        auto res = irBuilder->CreateNSWMul(mul1, mul2);

        irBuilder->CreateStore(res, dstRegPtr);
        irBuilder->CreateRetVoid();
    }

    // ---------------------------------MPYUS (.unit) scst5,xslsb16, sint ---------------------------------------------
    {
        llvm::Function *mpyusM = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "MPYUSKind2",
                                                        module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", mpyusM);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc1LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto cst5 = getCstN(module, irBuilder, 5);
        auto scst5 = irBuilder->CreateSExt(cst5, irBuilder->getInt32Ty());

        auto src2LSB = irBuilder->CreateTrunc(src2Reg, irBuilder->getIntNTy(16));
        auto mul2 = irBuilder->CreateSExt(src2LSB, irBuilder->getInt32Ty());
        auto res = irBuilder->CreateNSWMul(scst5, mul2);

        irBuilder->CreateStore(res, dstRegPtr);
        irBuilder->CreateRetVoid();
    }
}

std::shared_ptr<Instruction> MPYUS::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<MPYUS> mpyus;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<10>(data, 2, 0b1110100000) && existStandardCondition(data)) {
            mpyus = std::make_shared<MPYUS>(dspInstInfo->getAddress(), baseCycle);
            mpyus->creg_z = extractBits<4>(data, 28);
            mpyus->dst = extractBits<5>(data, 23).to_ulong();
            mpyus->src2 = extractBits<5>(data, 18).to_ulong();
            mpyus->src1 = extractBits<5>(data, 13).to_ulong();
            mpyus->x = data[12];
            mpyus->s = data[1];
            mpyus->p = data[0];
            if (mpyus->s == 0) {
                mpyus->funcUnit = FuncUnit::M1;
            } else {
                mpyus->funcUnit = FuncUnit::M2;
            }
            mpyus->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b1111000000) && existStandardCondition(data)) {
            mpyus = std::make_shared<MPYUS>(dspInstInfo->getAddress(), baseCycle);
            mpyus->creg_z = extractBits<4>(data, 28);
            mpyus->dst = extractBits<5>(data, 23).to_ulong();
            mpyus->src2 = extractBits<5>(data, 18).to_ulong();
            mpyus->cst = extractBits<5>(data, 13).to_ulong();
            mpyus->x = data[12];
            mpyus->s = data[1];
            mpyus->p = data[0];
            if (mpyus->s == 0) {
                mpyus->funcUnit = FuncUnit::M1;
            } else {
                mpyus->funcUnit = FuncUnit::M2;
            }
            mpyus->instFormat = InstFormat::Kind2;
        }
    }
    return mpyus;
}

void MPYUS::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {
    static u32 src1LSB = 0;
    static u32 src2LSB = 0;
    if (executePhase == ExecutePhase::E1) {
        switch (instFormat) {
            case InstFormat::Kind1:
                src1LSB = *src1LoRegPtr & 0x0000ffff;
                src2LSB = *src2LoRegPtr & 0x0000ffff;
                break;
            case InstFormat::Kind2:
                src2LSB = *src2LoRegPtr & 0x0000ffff;
            default:
                break;
        }
    } else if (executePhase == ExecutePhase::E2) {
        if ((src2LSB & 0x00008000) != 0) {
            src2LSB |= 0xffff0000;
        }
        i32 res = 0;
        switch (instFormat) {
            case InstFormat::Kind1:
                res = static_cast<i32>(src1LSB) * static_cast<i32>(src2LSB);
                *dstLoRegPtr = static_cast<u32>(res);
                break;
            case InstFormat::Kind2:
                res = static_cast<i32>(src1LSB) * static_cast<i32>(cst);
                break;
            default:
                break;
        }
        *dstLoRegPtr = static_cast<u32>(res);
    }
}

std::string MPYUS::toString() const {
    std::string res;
    res += getStandardConditionString() + " " + m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " ";
    switch (instFormat) {
        case InstFormat::Kind1:
            res += getSrc1Reg32Name() + "," + getSrc2Reg32Name() + "," + getDstReg32Name();
            break;
        case InstFormat::Kind2:
            res = res + dec2hex(cst) + "," + getSrc2Reg32Name() + "," + getDstReg32Name();
            break;
        default:
            break;
    }
    return res;
}

std::shared_ptr<DspInstruction> MPYUS::clone() {
    return std::make_shared<MPYUS>(*this);
}
